/*
 *  Copyright (C) 2011 JiangHongTiao <jjurco.sk_gmail.com>
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package sk.lieskove.jianghongtiao.websearch.api.google.search;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import sk.lieskove.jianghongtiao.common.exceptions.HTTPResponseException;
import sk.lieskove.jianghongtiao.common.utils.FileUtils;
import sk.lieskove.jianghongtiao.common.utils.PropertiesUtils;
import sk.lieskove.jianghongtiao.multithreaddownloader.MultiThreadDownloadManager;
import sk.lieskove.jianghongtiao.multithreaddownloader.MultiThreadDownloader;
import sk.lieskove.jianghongtiao.multithreaddownloader.document.RemoteDocument;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;

/**
 *
 * @author xjuraj
 */
public class GoogleWebSearchImpl implements GoogleWebSearch {

    private static final String URL_ENCODING = "UTF-8";
    private final Map<String, String> queryParams = new HashMap<String, String>();
    private final String[] availParamNames = {"q", "userip", "rsz", "hl", "key",
        "start",
        "callback", "context", "cx", "cref", "safe", "lr", "filter", "gl"};
    private static Logger log = Logger.getLogger(GoogleWebSearchImpl.class);

    private boolean inArray(String param) {
        for (int i = 0; i < availParamNames.length; i++) {
            String item = availParamNames[i];
            if (item.equals(param)) {
                return true;
            }
        }
        return false;
    }

    public GoogleWebSearchImpl() {
        //load default params from file settings.xml
//        try {
        PropertiesUtils p = new PropertiesUtils(GoogleWebSearchImpl.class);
        //p.loadFromXML(new FileInputStream(new File("config/settings.xml")));
        String paramSettings;
        for (int i = 1; i < availParamNames.length; i++) {
            String param = availParamNames[i];
            if ((paramSettings = p.getProperty("google.web.search." + param))
                    != null
                    && !paramSettings.equals("")) {
                addWSParam(param, paramSettings);
            }
        }
//        } catch (InvalidPropertiesFormatException ex) {
//            Logger.getLogger(GoogleWebSearchImpl.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (IOException ex) {
//            Logger.getLogger(GoogleWebSearchImpl.class.getName()).log(Level.SEVERE, null, ex);
//        }


    }

    private String getParamsUrl() {
        String result = "";
        String resultNE = "";
        for (Iterator<String> it = queryParams.keySet().iterator(); it.hasNext();) {
            String key = it.next();
//            try {
//                result += key + "=" + URLEncoder.encode(queryParams.get(key), URL_ENCODING);
//                
                result += key + "=" + queryParams.get(key).trim();
//                
//            } catch (UnsupportedEncodingException ex) {
//                log.error("Encoding is not supported: "+URL_ENCODING);
//            }

            if (it.hasNext()) {
                result += "&";
            }
        }
        log.debug("GOOGLE URL: "+result);
//        System.out.println();
//        System.out.println("GOOGLE URL (NE): "+resultNE);
        return result;
    }

    @Override
    public JSONObject search() {
        JSONObject json = null;
        URL url = null;
        try {
            // This example request includes an optional API key which you will need to
            // remove or replace with your own key.
            // Read more about why it's useful to have an API key.
            // The request also includes the userip parameter which provides the end
            // user's IP address. Doing so will help distinguish this legitimate
            // server-side traffic from traffic which doesn't come from an end-user.
            url = new URL("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&"
                    + getParamsUrl());
            MultiThreadDownloadManager manager = MultiThreadDownloader.
                    getInstance();
            log.debug(url.toString());
            UUID uuid = manager.addLink(url, 100);
            RemoteDocument document = manager.getDownload(uuid);
            if (document.getFile() != null) {
                json = new JSONObject(FileUtils.read(document.getFile()));
            } else {
                json = null;
            }


//            GWSResponse res = new GWSResponseImpl(json);
//            System.out.println(res.getResponseData().getResult(1).getTitle());
            // now have some fun with the results...
        } catch (JSONException ex) {
            log.error("JSON object is not in correct format!");
        } catch (MalformedURLException ex) {
            log.error("URL has bad format: "+url);
        } catch (IOException ex) {
            log.error("I/O exception thrown.", ex);
        }

        return json;
    }

    @Override
    public final Map<String, String> addWSParam(String param, String value) {
        if (param.equals("v")) {
            throw new IllegalArgumentException("Param value cannot be 'v'.");
        }
        try {
            if (inArray(param)) {
                queryParams.put(param, URLEncoder.encode(value, "UTF-8"));
//                queryParams.put(param, value);
//                queryParams.put(param, URLEncoder.encode(value, "ISO-8859-2"));
            }
        } catch (UnsupportedEncodingException ex) {
            log.error("Encoding is not supported!");
        }
        return Collections.unmodifiableMap(queryParams);
    }

    @Override
    public Map<String, String> removeWSParam(String param) {
        if (param.equals("v")) {
            return Collections.unmodifiableMap(queryParams);
        }
        queryParams.remove(param);
        return Collections.unmodifiableMap(queryParams);
    }

    @Override
    public Map<String, String> getListOfParams() {
        return Collections.unmodifiableMap(queryParams);
    }

    private GWSResponse[] getEmptyResultArray(Integer resultCount, String rsz) {
        Integer resC;
        Integer size;
        if (rsz == null) {
            resC = new Integer(4);
        } else {
            resC = new Integer(rsz);
        }
        if ((resultCount % resC) == 0) {
            size = new Integer(resultCount / resC);
        } else {
            size = new Integer((resultCount / resC) + 1);
        }
        return new GWSResponse[size];
    }
    
    @Override
    public GWSResponse search(Integer resultCount) throws HTTPResponseException {
        GWSResponse result = null;
        try {
            if (resultCount > 64) {
                log.info("Number of results is too large ("
                        + resultCount
                        + "), google probably throws an exception.");
            }
            String rsz = queryParams.get("rsz");
            int step = Math.min(resultCount, 8);
            queryParams.put("rsz", String.valueOf(step));

            for (int i = 0; i < resultCount; i += step) {
                addWSParam("start", String.valueOf(i));
                JSONObject search = search();
                if (search != null) {
                    if (result == null) {
                        result = new GWSResponseImpl(search);
                    } else {
                        GWSResponse searchResult = new GWSResponseImpl(search);
                        result.getResponseData().appendResults(
                                searchResult.getResponseData().getResults());
                    }
                }
                String newRsz = String.valueOf(Math.min(resultCount - step - i,
                        8));
                queryParams.put("rsz", newRsz);
            }

            queryParams.put("rsz", rsz);
        } catch (JSONException ex) {
            log.error("JSON object has bad format!");
        }
        return result;
    }

    /**
     * google API key for the application. Key can be generated on page: http://code.google.com/apis/loader/signup.html
     *
     * @param key google API key
     * @return actual instance of GoogleWebSearch
     */
    @Override
    public GoogleWebSearch withKey(String key) {
        addWSParam("key", key);
        return this;
    }

    /**
     * set language for the search
     *
     * @param lang localized search specified by language
     * @return actual instance of GoogleWebSearch
     */
    @Override
    public GoogleWebSearch withLanguage(GWSLanguage lang) {
        addWSParam("hl", lang.value());
        return this;
    }

    /**
     * set query for the search
     *
     * @param query search query
     * @return actual instance of GoogleWebSearch
     */
    @Override
    public GoogleWebSearch withQuery(String query) {
        addWSParam("q", query);
        return this;
    }
}
